package loadiconhandler

import (
	"crypto/md5"
	"fmt"
	"gmap-backend/internal/config"
	"gmap-backend/internal/server/vo"
	"gmap-backend/internal/util/imgutil"
	"gmap-backend/internal/util/pathutil"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"unsafe"
)

type FileLoadIcon struct {
}

func (owner *FileLoadIcon) CanHandle(appConfig *config.AppConfig, req *vo.PathReq) (bool, any) {
	urlMatch := strings.HasPrefix(req.Path, "file:///") || strings.HasPrefix(req.Path, "file://")
	return urlMatch, nil
}

func (owner *FileLoadIcon) Handle(appConfig *config.AppConfig, req *vo.PathReq, extra any) *vo.LoadIconResp {
	parseResult := pathutil.ParseValidPath(req.Path, req.CtxDir)
	if !parseResult.Succ {
		panic(fmt.Sprintf("未匹配到有效路径：\r\n%s", req.Path))
	}
	if parseResult.Dir {
		return &vo.LoadIconResp{Url: SPECIAL_PATH_FOLDER}
	}

	return GetFileIconUrl(appConfig, parseResult.ValidPath)
}

func GetFileIconUrl(appConfig *config.AppConfig, validPath string) *vo.LoadIconResp {
	// 结果路径
	// 取解析后的路径进行md5计算，这样只要最终路径一样，不管多少种路径写法，都只有一个缓存文件
	// 比如：c:\windows\abc.txt、%windir%\abc.txt、file:///c:/windows/abc.txt
	// 只要缓存文件存在，就不再重新计算图标
	md5 := fmt.Sprintf("%x", md5.Sum([]byte(validPath)))
	cachedResp := FindCachedIcon(appConfig, md5)
	if nil != cachedResp {
		return cachedResp
	}

	// 如果文件本身就是有效的图片且不是ico，则把文件本身复制到目标路径返回
	imgValid, imgType := imgutil.IsValidImgFile(validPath)
	if imgValid && "ico" != imgType {
		resultPath := filepath.Join(appConfig.PathConfig.CacheDir, md5+"."+imgType)
		resultUrl := "file:///" + strings.TrimSpace(strings.ReplaceAll(resultPath, "\\", "/"))

		source, _ := os.Open(validPath)
		defer source.Close()
		dest, _ := os.Create(resultPath)
		defer dest.Close()
		io.Copy(dest, source)
		return &vo.LoadIconResp{Url: resultUrl}
	}

	resultPath := filepath.Join(appConfig.PathConfig.CacheDir, md5+".png")
	resultUrl := "file:///" + strings.TrimSpace(strings.ReplaceAll(resultPath, "\\", "/"))
	to, _ := syscall.UTF16PtrFromString(resultPath)

	// 如果文件是msc文件，则取其中的<Icon/>行，并从指定的路径中读取图标，如果读取msc文件过程出现错误则不抛异常，直接执行后面的处理
	if strings.HasSuffix(strings.TrimSpace(strings.ToLower(validPath)), ".msc") {
		iconFound, iconPath, iconInd := getMscFileIconPathAndInd(validPath)
		if iconFound {
			from, _ := syscall.UTF16PtrFromString(iconPath)
			callRs, _, _ := appConfig.Win32LibConfig.LoadIconByIndProc.Call(uintptr(unsafe.Pointer(from)), uintptr(iconInd), uintptr(unsafe.Pointer(to)))
			if 0 == callRs {
				return &vo.LoadIconResp{Url: resultUrl}
			}
			panic(fmt.Sprintf("读取文件图标时出错：%s", iconPath))
		}
	}

	// 提取文件的图标并保存到目标路径后返回
	from, _ := syscall.UTF16PtrFromString(validPath)
	callRs, _, _ := appConfig.Win32LibConfig.LoadIconProc.Call(uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)))
	if 0 == callRs {
		return &vo.LoadIconResp{Url: resultUrl}
	}
	panic(fmt.Sprintf("读取文件图标时出错：%s", validPath))
}

var iconReg = regexp.MustCompile("[<]Icon.+?[>]")
var indexReg = regexp.MustCompile("Index\\s*=\\s*['\"]([0-9]+?)['\"]")
var pathReg = regexp.MustCompile("File\\s*=\\s*['\"](.+?)['\"]")

func getMscFileIconPathAndInd(mscFilePath string) (succ bool, path string, ind int) {
	defer func() {
		if e := recover(); nil != e {
			succ = false
			path = ""
			ind = -1
		}
	}()

	bs, err := os.ReadFile(mscFilePath)
	if nil != err {
		panic(err)
	}

	// 获取Icon标签内容
	// <Icon Index="2" File="%windir%\system32\mycomput.dll">
	xmlCont := string(bs)
	groups := iconReg.FindStringSubmatch(xmlCont)
	if 0 == len(groups) {
		return false, "", -1
	}

	// 获取路径
	pathMatches := pathReg.FindStringSubmatch(groups[0])
	if len(pathMatches) < 2 {
		return false, "", -1
	}
	path = pathMatches[1]
	pathResult := pathutil.ParseValidPath(path, mscFilePath)
	if !pathResult.Succ || !pathResult.File {
		panic("path not valid")
	}
	path = pathResult.ValidPath

	// 获取索引
	ind = 0
	indexMatches := indexReg.FindStringSubmatch(groups[0])
	if len(indexMatches) >= 2 {
		ind, err = strconv.Atoi(indexMatches[1])
		if nil != err {
			panic(err)
		}
	}
	return true, path, ind
}
